import Utils from 'src/utils/Utils';
import { Product } from './../product/entities/product.entity';
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';
import { CreateStockDto } from './dto/create-stock.dto';
import { UpdateStockDto } from './dto/update-stock.dto';
import { Stock } from './entities/stock.entity';
import { SearchDto } from './dto/search.dto';
import * as dayjs from 'dayjs';
import Decimal from 'decimal.js';
import { getCategory, getSource, getStatus } from '../product/enum/Enum';

@Injectable()
export class StockService {
  constructor(
    @InjectRepository(Stock)
    private stockService: Repository<Stock>,
    @InjectRepository(Product)
    private productService: Repository<Product>,
    @InjectEntityManager()
    private readonly entityManager: EntityManager,
  ) { }

  // 工具转换函数
  formatResult(list: Stock[]) {
    return list.forEach(_ => {
      _.product.categoryDesc = getCategory(_.product.category)
      _.product.sourceDesc = getSource(_.product.source)
      _.product.statusDesc = getStatus(_.product.status)
    })
  }

  // 应该开始事务，暂时未找到合适的库
  async create(createStockDto: CreateStockDto,) {
    const product = await this.productService.findOne({ where: { pId: createStockDto.pId, delete: false } })
    if (!product.pId) throw '未查询到该商品~'
    let count = product.stock
    if (createStockDto.type === 0) { // 入库
      count += createStockDto.total
      product.stock = count
    }
    if (createStockDto.type === 1) {// 出库
      if (product.stock < createStockDto.total) {
        throw '出库失败,库存不足！'
      }
      count -= createStockDto.total
      product.stock = count
    }

    product.updateTime = new Date()
    createStockDto.guid = Utils.guid()

    await this.stockService.save(createStockDto)
    await this.productService.save(product)
    return '操作成功~'
  }

  async updateStock(updateStockDto: UpdateStockDto) {
    const stock = await this.stockService.findOne({ where: { guid: updateStockDto.guid, delete: false } })
    if (!stock.guid) throw '未查询到该记录~'
    const product = await this.productService.findOne({ where: { pId: updateStockDto.pId, delete: false } })
    const type = stock.type // 0 入库 1 出库
    const count = updateStockDto.total - stock.total
    if (type === 0) {
      // 目标 原本
      // 16   8   库存增加
      // 8    16  库存减少
      product.stock += count
    }
    if (type === 1) {
      if (product.stock < count) {
        throw '修改失败,库存不足！'
      }
      // 目标 原本
      // 16   8   库存减少 销售更多
      // 8    16  库存增加
      product.stock -= count
    }

    product.updateTime = new Date()
    stock.updateTime = new Date()

    stock.price = updateStockDto.price
    stock.total = updateStockDto.total
    stock.sum = updateStockDto.sum
    stock.postage = updateStockDto.postage
    stock.orderDate = updateStockDto.orderDate
    await this.productService.save(product)
    await this.stockService.save(stock)
    return '操作成功~'
  }

  async remove(guid: string) {
    const stock = await this.stockService.findOne({ where: { guid, delete: false } })
    if (!stock?.guid) throw '未查询到该记录~'
    const product = await this.productService.findOne({ where: { pId: stock.pId, delete: false } })
    const type = stock.type // 0 入库 1 出库
    if (type === 0) {
      product.stock -= stock.total
    }
    if (type === 1) {
      product.stock += stock.total
    }

    product.updateTime = new Date()
    stock.updateTime = new Date()
    stock.delete = true

    await this.stockService.save(stock)
    return this.productService.save(product)
  }

  /**
   * 条件分页 查询
   * @param searchDto
   * @returns
   */
  async findAll(searchDto: SearchDto): Promise<{ result: Stock[], total: number } | Stock[]> {
    const { page, pageSize, type, pId } = searchDto
    const sql = this.stockService.createQueryBuilder('s')
      .leftJoinAndMapOne('s.product', 'product', 'p', 's.pId = p.pId')
    if (pId) {
      sql.andWhere('s.p_id = :pId', { pId: pId })
    }
    if (type !== undefined) {
      sql.andWhere('s.type = :type', { type })
    }

    sql.andWhere('s.delete = :delete', { delete: false })
    let response = null
    if (page && pageSize) {
      const result = await sql.skip((page - 1) * pageSize).take(pageSize).getMany();
      this.formatResult(result)
      let total = await sql.getCount()
      total = Math.ceil(total / pageSize)
      response = { result, total }
    } else {
      response = await sql.getMany()
      this.formatResult(response)
    }
    return response
  }

  /**
   * 获取商品 出库入库记录
   * @param createDto
   * @returns
   */
  async loadRecordByPId(createDto: CreateStockDto) {
    const { pId } = createDto
    const searchDto = new SearchDto()
    searchDto.pId = pId
    const result = await this.findAll(searchDto) as Stock[]
    const data = result.reduce((total: { out: Stock[], put: Stock[] }, _: Stock) => {
      if (_.type === 0) {
        total.put.push(_)
      }
      if (_.type === 1) {
        total.out.push(_)
      }
      return total
    }, { out: [], put: [] })
    return data
  }

  /**
   * 前几日交易额交易额
   * @returns {string}
   */
  async getDealByDate(day: number): Promise<string> {
    const after = Utils.getDateByNow(day)
    const before = Utils.getDateByNow(-1)
    let startDate = undefined, endDate = undefined
    if (day !== 0) {
      startDate = new Date(`${after} 00:00:01`)
      endDate = new Date(`${before} 23:59:59`)
    }
    const sql = this.stockService.createQueryBuilder('s')
    sql.where('s.type = :type', { type: true })

    if (startDate) {
      sql.andWhere('s.create_time >= :startDate', { startDate })

    }
    if (endDate) {
      sql.andWhere('s.create_time <= :endDate', { endDate })
    }
    const result = await sql.getMany()
    const total = result.reduce((total, item) => {
      total = new Decimal(item.sum).add(total)
      return total
    }, new Decimal(0))
    return total.toString()
  }

  /**
   * 日期范围订单数
   * @param day
   * @returns
   */
  async getOrdersByDate(start: number, end: number): Promise<number> {
    const after = Utils.getDateByNow(start)
    const before = Utils.getDateByNow(end)
    let startDate = undefined, endDate = undefined
    if (start && end) {
      startDate = new Date(`${after} 00:00:01`)
      endDate = new Date(`${before} 23:59:59`)
    }
    const sql = this.stockService.createQueryBuilder('s')
    sql.where('s.type = :type', { type: true })
    sql.andWhere('s.delete = :delete', { delete: false })

    if (startDate) {
      sql.andWhere('s.create_time >= :startDate', { startDate })
    }
    if (endDate) {
      sql.andWhere('s.create_time <= :endDate', { endDate })
    }
    const result = await sql.getMany()
    return result.length
  }

  /**
   * 交易信息
   * @returns
   */
  async getDeal() {
    // 昨日交易额
    const yesterday = await this.getDealByDate(-1)
    // 月交易额
    const month = await this.getDealByDate(-30)
    // 累计交易额
    const total = await this.getDealByDate(0)
    // 昨日订单
    const yesterdayOrders = await this.getOrdersByDate(-1, -1)
    // 月订单
    const monthOrders = await this.getOrdersByDate(-30, -1)
    // 累计订单
    const totalOrders = await this.getOrdersByDate(0, 0)
    return { yesterday, month, total, totalOrders, yesterdayOrders, monthOrders }
  }

  /**
   * 首页柱状图
   */
  async barGraph() {
    // 近30天
    const startDate = Utils.getDateByNow(-30)
    const endDate = Utils.getDateByNow(0)
    const sql = this.stockService.createQueryBuilder('s')
    sql.where('s.type = :type', { type: true })
    sql.andWhere('s.delete = :delete', { delete: false })
    sql.andWhere('s.create_time >= :startDate', { startDate })
    sql.andWhere('s.create_time < :endDate', { endDate })
    sql.groupBy('create_time')
    sql.orderBy('create_time', 'DESC')
    console.log('bargraph :>> ', sql.getSql())
    const result = await sql.getMany()

    const map = new Map()
    let key = undefined

    // 初始化 map
    for (let index = -30; index < 0; index++) {
      key = Utils.getDateByNow(index)
      map.set(key, 0)
    }
    let value = new Decimal(0)
    result.forEach(item => {
      key = dayjs(item.createTime).format('YYYY-MM-DD')
      value = new Decimal(map.get(key) || 0)
      value = new Decimal(item.sum).add(value)
      map.set(key, value)
    })

    return Array.from(map.values())
  }


  /**
   * 首页饼图
   */
  async nightingale() {
    // 近30天
    const startDate = Utils.getDateByNow(-30)
    const endDate = Utils.getDateByNow(0)
    const sql = `
      SELECT
        p.p_name as name,
        s.p_id as pId,
        SUM(s.total) as count,
        SUM(s.sum) as sum
      FROM stock_record s
      INNER JOIN product p
      ON s.p_id = p.p_id
      WHERE
        s.delete = FALSE AND s.type = 1 AND s.create_time > DATE(?) AND s.create_time < DATE(?)
      GROUP BY s.p_id`

    const result = await this.entityManager.query(sql, [startDate, endDate]);
    result.forEach((item: { name: string, count: number, sum: string }) => {
      item.name = `${item.name}(${item.count}个)`
    });
    return result;
  }

  weekBuild(week: string[], result: any[]) {
    const map = new Map<string, string>()
    result.forEach((_: { date: Date, data: string }) => {
      map.set(dayjs(_.date).format('MM-DD'), _.data)
    })
    return week.reduce((result: string[], _: string) => result.push(map.get(_) || '0') && result, [])
  }

  // 周内 每日交易额
  async getWeekDeal(week: string[], day: number) {
    const sql = `
      SELECT
        DATE(create_time) as date,
        SUM(sum) as data
      FROM stock_record s
      WHERE s.delete = FALSE AND type = 1 AND create_time >= CURDATE() - INTERVAL ? DAY AND create_time < CURDATE()
      GROUP BY DATE(create_time)
      ORDER BY create_time ASC;
    `
    const result = await this.entityManager.query(sql, [day]);
    return this.weekBuild(week, result)
  }

  // 周内 每日订单数
  async getWeekOrders(week: string[], day: number) {
    const sql = `
      SELECT
        DATE(create_time) as date,
        count(*) as data
      FROM stock_record s
      WHERE s.delete = FALSE AND type = 1 AND create_time >= CURDATE() - INTERVAL ? DAY AND create_time < CURDATE()
      GROUP BY DATE(create_time)
      ORDER BY create_time ASC;
    `
    const result = await this.entityManager.query(sql, [day]);
    return this.weekBuild(week, result)
  }

  // 周内 每日出货数
  async getWeekOutProduct(week: string[], day: number) {
    const sql = `
      SELECT
        DATE(create_time) as date,
        SUM(total) as data
      FROM stock_record s
      WHERE s.delete = FALSE AND type = 1 AND create_time >= CURDATE() - INTERVAL ? DAY AND create_time < CURDATE()
      GROUP BY DATE(create_time)
      ORDER BY create_time ASC;
    `
    const result = await this.entityManager.query(sql, [day]);
    return this.weekBuild(week, result)
  }

  // 周内 每日进货数
  async getWeekInProduct(week: string[], day: number) {
    const sql = `
      SELECT
        DATE(create_time) as date,
        SUM(total) as data
      FROM stock_record s
      WHERE s.delete = FALSE AND type = 0 AND create_time >= CURDATE() - INTERVAL ? DAY AND create_time < CURDATE()
      GROUP BY DATE(create_time)
      ORDER BY create_time ASC;
    `
    const result = await this.entityManager.query(sql, [day]);
    return this.weekBuild(week, result)
  }

  // 周内 每日进货额度
  async getWeekInTotal(week: string[], day: number) {
    const sql = `
      SELECT
        DATE(create_time) as date,
        SUM(sum) as data
      FROM stock_record s
      WHERE s.delete = FALSE AND type = 0 AND create_time >= CURDATE() - INTERVAL ? DAY AND create_time < CURDATE()
      GROUP BY DATE(create_time)
      ORDER BY create_time ASC;
    `
    const result = await this.entityManager.query(sql, [day]);
    return this.weekBuild(week, result)
  }

  /**
   * 折现图
   */
  async chartLine() {
    const dataType = ['交易额', '订单数', '出货数', '进货数', '进货额']
    const dataX = [], day = 15
    for (let i = day * -1; i < 0; i++) {
      dataX.push(Utils.getDateByNow(i, undefined, 'MM-DD'))
    }
    const weekDeal = await this.getWeekDeal(dataX, day)
    const weekOrders = await this.getWeekOrders(dataX, day)
    const weekOutProduct = await this.getWeekOutProduct(dataX, day)
    const weekInProduct = await this.getWeekInProduct(dataX, day)
    const weekInTotal = await this.getWeekInTotal(dataX, day)
    const dataY = [
      {
        name: dataType[0],
        type: 'line',
        stack: 'Total',
        data: weekDeal
      },
      {
        name: dataType[1],
        type: 'line',
        stack: 'Total',
        data: weekOrders
      },
      {
        name: dataType[2],
        type: 'line',
        stack: 'Total',
        data: weekOutProduct
      },
      {
        name: dataType[3],
        type: 'line',
        stack: 'Total',
        data: weekInProduct
      },
      {
        name: dataType[4],
        type: 'line',
        stack: 'Total',
        data: weekInTotal
      },
    ]

    return { dataType, dataX, dataY }
  }

  async productPut() {
    const query = `
      SELECT
        p.p_id as pId, p.p_ip as pIp, p.p_name as name, s.guid, s.order_date as orderDate, s.price,
				s.total, s.sum as sum, s.postage
      FROM product p
      LEFT JOIN stock_record s ON p.p_id = s.p_id
      WHERE s.type = 0 AND s.delete = 0
      ORDER BY name DESC;
    `;
    const result = await this.productService.query(query);
    return result
  }

  async productOut() {
    const query = `
      SELECT
        p.p_id as pId, p.p_ip as pIp, p.p_name as name, s.guid, s.order_date as orderDate, s.price,
				s.total, s.sum as sum, s.postage
      FROM product p
      LEFT JOIN stock_record s ON p.p_id = s.p_id
      WHERE s.type = 1 AND s.delete = 0
      ORDER BY name DESC;
    `;
    const result = await this.productService.query(query);

    // const sql = this.productService.createQueryBuilder('p')
    // const result = await sql.select(['p.p_ip', 'p.p_name', 'SUM(s.sum) as sum', 'SUM(s.total) as total'])
    //   .leftJoin(Stock, 's', 'p.p_id = s.p_id')
    //   .andWhere('s.type = :type', { type: 1 })
    //   .groupBy('p.p_id')
    //   .orderBy('sum', 'DESC')
    //   .getMany()
    // console.log('object :>> ', sql.getSql())
    return result
  }
}
